Skip to content

fix(app): make multi-file environment import resilient and idempotent#8374

Open
NicolasCV wants to merge 4 commits into
usebruno:mainfrom
NicolasCV:bugfix/env-import-multi-file-resilience
Open

fix(app): make multi-file environment import resilient and idempotent#8374
NicolasCV wants to merge 4 commits into
usebruno:mainfrom
NicolasCV:bugfix/env-import-multi-file-resilience

Conversation

@NicolasCV

@NicolasCV NicolasCV commented Jun 25, 2026

Copy link
Copy Markdown

Fixes #8373

Description

Importing several environment files at once is fragile. One bad file makes the whole batch fail, a failure partway through leaves some environments written to disk behind a generic error, and re-importing the same files quietly creates … copy duplicates. This PR reads and parses each file on its own so the batch survives bad input, skips environments that already exist, and replaces the generic toast with a short summary of what happened.

Root cause

Three separate problems in handleImportEnvironment and processEnvironments:

  1. Every file was read up front in a single readMultipleFiles(Array.from(files)) call and the format was detected from the first file only (detectEnvironmentFormat(parsedFiles[0].content)), then one importer was run over every file. A malformed file makes the read throw and aborts the whole import; a selection that mixes Bruno and Postman exports throws inside the importer and aborts the rest. Either way nothing gets imported.

  2. processEnvironments looped with await dispatch(action) inside a single try. If the third of five environments failed, the first two were already written to disk, the loop threw, and the user saw An error occurred while importing the environment(s) with no idea which ones made it.

  3. The client never checks existing names. The only guard is the Electron handler renderer:create-environment, which calls generateUniqueName(), so re-importing the same file produces X copy, then X copy 2, and so on.

The fix

Each file is now read and parsed on its own — the loop calls readMultipleFiles([file]) per file and detects its format individually, so a single malformed JSON only fails that file and is collected as a failure instead of aborting the batch. A mixed Bruno/Postman selection imports each file with its own detected format.

Each environment is dispatched in its own try/catch and failures are collected, so one failure doesn't take down the rest of the batch.

Before importing, the code builds a set of existing names (collection.environments for collection scope, the globalEnvironments selector for global scope) keyed by sanitizeName, the same function the Electron handler uses, and skips any name that already exists. The incoming batch is also deduped against itself. The name is normalized once with sanitizeName and that value is reused for the dedupe check and for the importEnvironment / addGlobalEnvironment payloads, so the collection and global branches behave identically and never re-import raw names. Re-importing the same file is now a no-op instead of stacking copies.

Feedback is one summary: an Imported N environments. toast, plus a note of how many were skipped, unnamed, or failed. The modal stays open only when nothing was imported and nothing was skipped so the user can retry; otherwise it closes and fires onEnvironmentCreated only if something was actually imported.

The change is contained to the modal component. Importers, actions, and the Electron handler are untouched, and it reuses the existing readMultipleFiles, importPostmanEnvironment, importBrunoEnvironment, sanitizeName, importEnvironment, and addGlobalEnvironment.

Before / After

Scenario Before After
One malformed file among several Whole import aborts, nothing imported Bad file reported, the rest imported
Mixed Bruno + Postman selection Wrong importer throws, import aborts Each file imported with its own detected format
One env fails mid-batch Partial write plus a generic error The rest import, the failure is listed
Re-import the same file Stacks X copy, X copy 2 Names that already exist are skipped
Result feedback N environments imported successfully or a generic error Imported N environments. plus a skipped/unnamed/failed note

Verification

Tested manually in the app, in both collection and global scope:

  • A folder of valid Bruno envs imports cleanly with one success toast.
  • A corrupt JSON in the set is reported while the rest import.
  • A mixed Bruno and Postman selection imports both formats.
  • Re-importing the same set imports nothing and skips everything already present, with no copy duplicates on disk.
  • An env with no name is counted under "had no name".

ESLint passes on the changed files. The change is contained to the modal component, with no API or behavior changes to importers, actions, or the Electron handler. Added a Jest spec for the modal (ImportEnvironmentModal/index.spec.js, 8 tests, all passing) covering mixed Bruno/Postman selections, a malformed file alongside valid ones, duplicate-name skips, unnamed environments, partial dispatch failures, the "nothing valid found" path, and name normalization across the collection and global branches.


  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Summary by CodeRabbit

  • New Features

    • Multi-file environment import can handle mixed Postman/Bruno files in one batch.
    • Import now presents a detailed batch summary (imported, skipped duplicates, unnamed) and shows specific failure notes when issues occur.
  • Bug Fixes

    • Duplicate detection and import routing are improved by normalizing environment names.
    • Import failures for individual environments no longer prevent other valid environments from importing.
    • The import dialog remains open when no environments were effectively imported.
  • Tests

    • Added coverage for mixed imports, partial failures, duplicates, unnamed handling, and summary/toast behavior (including name normalization).

Importing multiple environment files at once aborted the whole batch on a
single bad or mismatched file, left partial imports behind a generic error,
and silently piled up "X copy" duplicates on re-import.

- Parse each file independently with its own format detection, so one
  malformed file or a mixed Bruno/Postman selection no longer aborts the import.
- Import each environment in its own try/catch and collect failures instead of
  throwing mid-loop after some were already written.
- Skip environments whose sanitized name already exists (collection or global
  scope), deduping within the batch too, so re-import is a no-op.
- Replace the generic toast with one summary: imported count plus an itemized
  skipped/unnamed/failed note; keep the modal open only on total failure.
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1a494ca3-0972-4e2b-9ce8-7938cf61ce44

📥 Commits

Reviewing files that changed from the base of the PR and between 3d81bff and 79ce252.

📒 Files selected for processing (1)
  • packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.spec.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.spec.js

Walkthrough

The environment import modal now reads each selected file independently, detects file formats per input, skips duplicate names, and reports imported, skipped, unnamed, and failed environments.

Changes

Environment import flow

Layer / File(s) Summary
Duplicate checks and batch summary
packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js
Adds Redux-backed existing-name lookup, normalizes environment names, skips duplicates, counts unnamed entries, and returns imported/skipped/unnamed/failure totals.
Per-file mixed-format import
packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js
Changes the modal to parse files individually, detect Postman or Bruno format per file, collect parse/import failures, and close only after successful progress.
Test scaffolding
packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.spec.js
Adds mocks, fixtures, and helpers for multi-file import scenarios.
Import behavior coverage
packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.spec.js
Adds tests for mixed-format imports, partial failures, duplicate skipping, unnamed environments, dispatch failures, no-valid-input handling, and sanitized name passing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • bijin-bruno
  • naman-bruno

Poem

A modal hummed with file-by-file grace,
It counted each name and each parsing case.
Bruno and Postman entered the dance,
With skips and with failures both given a glance.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: resilient, idempotent multi-file environment import.
Linked Issues check ✅ Passed The changes address the issue requirements: per-file parsing, mixed-format support, partial-failure handling, dedupe skipping, and clear import summaries.
Out of Scope Changes check ✅ Passed No clearly unrelated code changes are evident; the added logic and tests support the environment import fix and its edge cases.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js (1)

31-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Move the import workflow into a co-located hook.

The component now owns file parsing, duplicate checks, Redux writes, and toast side effects. Extracting this into a useImportEnvironment hook would keep the modal focused on UI wiring. As per coding guidelines, “MUST: Prefer custom hooks for business logic, data fetching, and side-effects in React.”

Also applies to: 109-142

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js`
around lines 31 - 92, The import workflow logic in ImportEnvironmentModal is too
mixed into the component, including parsing, duplicate detection, Redux
dispatches, and toast handling. Move the processEnvironments flow and related
side effects into a co-located custom hook such as useImportEnvironment, and
keep ImportEnvironmentModal focused on UI/state wiring by calling the hook for
the import action and result handling.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js`:
- Line 11: The batch import flow in ImportEnvironmentModal still aborts when
readMultipleFiles(Array.from(files)) hits one bad file, so change the
file-processing path to handle each selected file independently. Update the
logic around readMultipleFiles and the per-file importer in
ImportEnvironmentModal so a malformed JSON only affects that file, while valid
files continue through the existing import routine.
- Around line 36-61: Normalize environment names once in ImportEnvironmentModal
before both deduping and importing, instead of sanitizing only for the Set key.
In the ImportEnvironmentModal loop, use the sanitized value from sanitizeName
consistently for the seen check, the toImport list, and the
addGlobalEnvironment/importEnvironment payloads so the global and collection
branches behave the same and never re-import raw names.
- Around line 31-92: The environment import flow in processEnvironments needs
test coverage for the new batch summary states. Add/extend tests under
tests/environments/import-environment to cover mixed Bruno/Postman selections,
malformed files with valid imports, duplicate-name skips, unnamed environments,
and partial dispatch failures, asserting the toast summary and returned
imported/skipped/unnamed/failures counts from ImportEnvironmentModal.

---

Nitpick comments:
In
`@packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js`:
- Around line 31-92: The import workflow logic in ImportEnvironmentModal is too
mixed into the component, including parsing, duplicate detection, Redux
dispatches, and toast handling. Move the processEnvironments flow and related
side effects into a co-located custom hook such as useImportEnvironment, and
keep ImportEnvironmentModal focused on UI/state wiring by calling the hook for
the import action and result handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e8a00a39-8925-4a36-b5b7-a06e0856732e

📥 Commits

Reviewing files that changed from the base of the PR and between 1ceea07 and 4df0cba.

📒 Files selected for processing (1)
  • packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js

Comment on lines +31 to +92
const processEnvironments = async (environments, parseFailures = []) => {
const failures = [...parseFailures];

const named = [];
let unnamed = 0;
for (const env of environments) {
if (env.name && env.name !== 'undefined') named.push(env);
else unnamed++;
}

const existing = isGlobal ? globalEnvironments : collection?.environments || [];
const seen = new Set(existing.map((e) => sanitizeName(e.name || '')));

const toImport = [];
let skipped = 0;
for (const env of named) {
const key = sanitizeName(env.name);
if (seen.has(key)) {
skipped++;
} else {
toast.error('Failed to import environment: env has no name');
return false;
seen.add(key);
toImport.push(env);
}
});

if (validEnvironments.length === 0) {
toast.error('No valid environments found to import');
return;
}

try {
// Process environments sequentially to ensure unique name checking considers previously imported environments
let importedCount = 0;
for (const environment of validEnvironments) {
let imported = 0;
for (const environment of toImport) {
try {
const action = isGlobal
? addGlobalEnvironment({ name: environment.name, variables: environment.variables, color: environment.color })
: importEnvironment({ name: environment.name, variables: environment.variables, color: environment.color, collectionUid: collection?.uid });

await dispatch(action);
importedCount++;
imported++;
} catch (error) {
console.error(`Failed to import environment "${environment.name}":`, error);
failures.push({ name: environment.name, message: error?.message || String(error) });
}
}

if (imported > 0) {
toast.success(`Imported ${imported} environment${imported > 1 ? 's' : ''}.`);
}

toast.success(`${importedCount > 1 ? `${importedCount} environments` : 'Environment'} imported successfully`);
} catch (error) {
toast.error('An error occurred while importing the environment(s)');
console.error(error);
throw error;
const notes = [];
if (skipped > 0) notes.push(`${skipped} already existed and ${skipped > 1 ? 'were' : 'was'} skipped`);
if (unnamed > 0) notes.push(`${unnamed} had no name`);
if (failures.length > 0) {
const names = failures.map((f) => f.name).slice(0, 3).join(', ');
const more = failures.length > 3 ? ` and ${failures.length - 3} more` : '';
notes.push(`${failures.length} failed (${names}${more})`);
}

if (notes.length > 0) {
const message = notes.join('; ') + '.';
if (failures.length > 0) toast.error(message);
else toast(message);
} else if (imported === 0) {
toast.error('No valid environments found to import.');
}

return { imported, skipped, unnamed, failures };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Look for tests covering the Import Environment modal and new batch outcome messages.
# Expected: specs or tests asserting mixed-format import, duplicate skips, malformed-file tolerance, and summary feedback.

rg -n --glob '*.{js,jsx,ts,tsx}' \
  'ImportEnvironmentModal|import-environment|import-global-environment|already existed|No valid environments|failed \(|had no name|mixed.*Postman|mixed.*Bruno' \
  packages/bruno-app tests

Repository: usebruno/bruno

Length of output: 7650


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the modal implementation.
sed -n '1,240p' packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js

printf '\n--- TEST FILES ---\n'
# List relevant import-environment tests.
find tests/environments/import-environment -type f \( -name '*.spec.ts' -o -name '*.spec.js' -o -name '*.spec.jsx' -o -name '*.spec.tsx' \) | sort

printf '\n--- COLLECTION BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts

printf '\n--- GLOBAL BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts

printf '\n--- COLLECTION POSTMAN/ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/collection-env-import.spec.ts

printf '\n--- GLOBAL ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/global-env-import.spec.ts

printf '\n--- COLOR IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/env-color-import/env-color-import.spec.ts

Repository: usebruno/bruno

Length of output: 34003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the modal implementation.
sed -n '1,240p' packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js

printf '\n--- TEST FILES ---\n'
# List relevant import-environment tests.
find tests/environments/import-environment -type f \( -name '*.spec.ts' -o -name '*.spec.js' -o -name '*.spec.jsx' -o -name '*.spec.tsx' \) | sort

printf '\n--- COLLECTION BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts

printf '\n--- GLOBAL BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts

printf '\n--- COLLECTION POSTMAN/ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/collection-env-import.spec.ts

printf '\n--- GLOBAL ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/global-env-import.spec.ts

printf '\n--- COLOR IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/env-color-import/env-color-import.spec.ts

Repository: usebruno/bruno

Length of output: 34003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the modal implementation.
sed -n '1,240p' packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js

printf '\n--- TEST FILES ---\n'
# List relevant import-environment tests.
find tests/environments/import-environment -type f \( -name '*.spec.ts' -o -name '*.spec.js' -o -name '*.spec.jsx' -o -name '*.spec.tsx' \) | sort

printf '\n--- COLLECTION BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts

printf '\n--- GLOBAL BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts

printf '\n--- COLLECTION POSTMAN/ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/collection-env-import.spec.ts

printf '\n--- GLOBAL ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/global-env-import.spec.ts

printf '\n--- COLOR IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/env-color-import/env-color-import.spec.ts

Repository: usebruno/bruno

Length of output: 34003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,240p' packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js

printf '\n--- TEST FILES ---\n'
find tests/environments/import-environment -type f \( -name '*.spec.ts' -o -name '*.spec.js' -o -name '*.spec.jsx' -o -name '*.spec.tsx' \) | sort

printf '\n--- COLLECTION BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts

printf '\n--- GLOBAL BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts

printf '\n--- COLLECTION POSTMAN/ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/collection-env-import.spec.ts

printf '\n--- GLOBAL ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/global-env-import.spec.ts

printf '\n--- COLOR IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/env-color-import/env-color-import.spec.ts

Repository: usebruno/bruno

Length of output: 34003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,240p' packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js

printf '\n--- TEST FILES ---\n'
find tests/environments/import-environment -type f \( -name '*.spec.ts' -o -name '*.spec.js' -o -name '*.spec.jsx' -o -name '*.spec.tsx' \) | sort

printf '\n--- COLLECTION BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts

printf '\n--- GLOBAL BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts

printf '\n--- COLLECTION POSTMAN/ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/collection-env-import.spec.ts

printf '\n--- GLOBAL ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/global-env-import.spec.ts

printf '\n--- COLOR IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/env-color-import/env-color-import.spec.ts

Repository: usebruno/bruno

Length of output: 34003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read only the relevant test files and search for the new batch outcome assertions.
rg -n -A 4 -B 4 \
  'already existed|had no name|No valid environments found to import|failed \(|toast\.success|toast\.error|ImportEnvironmentModal|import-environment' \
  tests/environments/import-environment packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js

Repository: usebruno/bruno

Length of output: 18189


Add batch outcome coverage for environment imports Add tests for the new summary states in tests/environments/import-environment/*: mixed Bruno/Postman selections, one malformed file alongside valid files, duplicate skips, unnamed environments, and partial import failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js`
around lines 31 - 92, The environment import flow in processEnvironments needs
test coverage for the new batch summary states. Add/extend tests under
tests/environments/import-environment to cover mixed Bruno/Postman selections,
malformed files with valid imports, duplicate-name skips, unnamed environments,
and partial dispatch failures, asserting the toast summary and returned
imported/skipped/unnamed/failures counts from ImportEnvironmentModal.

Source: Coding guidelines

Read each selected file on its own so a single malformed JSON no longer
aborts the whole batch — valid files continue through the import routine
and the bad file is reported in the summary.

Normalize environment names with sanitizeName once before deduping and
importing so the global and collection branches behave the same and never
re-import raw names.

Add tests covering mixed Bruno/Postman selections, malformed-with-valid
imports, duplicate-name skips, unnamed environments, partial dispatch
failures, and name normalization.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.spec.js (1)

120-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a same-batch duplicate case.

This suite checks duplicates against existing environments, but the new seen.add(name) path also deduplicates duplicates within the imported batch. A test for two incoming environments collapsing to the same sanitized name would cover the … copy regression this PR is meant to prevent. As per coding guidelines, "Add tests for any new functionality or meaningful changes."

Also applies to: 177-205

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.spec.js`
around lines 120 - 133, The current import-environment tests cover duplicates
against existing environments, but not duplicates within the same imported batch
handled by the new seen.add(name) path. Add a test in the ImportEnvironmentModal
spec, near the existing duplicate cases in renderModal/dropFiles, that imports
two environments whose sanitized names collapse to the same value (including the
“copy” suffix regression) and assert the second is skipped while the first is
created, with the expected toast and no extra dispatches.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.spec.js`:
- Around line 78-83: The unnamed Bruno test is bypassing the real importer
behavior, so it does not cover the actual contract for missing names. Update the
mock in ImportEnvironmentModal/index.spec.js to mirror importBrunoEnvironment by
normalizing unnamed environments to "Imported Environment" before returning from
passthrough, so processEnvironments() is exercised the same way as the real
Bruno importer. Keep the assertion focused on the Bruno import flow and the
unnamed case that feeds the importer contract.

---

Nitpick comments:
In
`@packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.spec.js`:
- Around line 120-133: The current import-environment tests cover duplicates
against existing environments, but not duplicates within the same imported batch
handled by the new seen.add(name) path. Add a test in the ImportEnvironmentModal
spec, near the existing duplicate cases in renderModal/dropFiles, that imports
two environments whose sanitized names collapse to the same value (including the
“copy” suffix regression) and assert the second is skipped while the first is
created, with the expected toast and no extra dispatches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8accbd45-c78a-42a6-811b-56114ae0db5c

📥 Commits

Reviewing files that changed from the base of the PR and between 4df0cba and ef9bccb.

📒 Files selected for processing (2)
  • packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js
  • packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.spec.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js

Guard non-string names before sanitizing and treat names that sanitize to
empty as unnamed, so a malformed name can't throw or import a blank-named
environment. Normalize the name once in the named/unnamed split and reuse it
for the dedupe key and import payloads.

Make the unnamed-environment test use the Postman path (the Bruno importer
defaults missing names) and add coverage for non-string / empty-after-sanitize
names.
@NicolasCV

NicolasCV commented Jun 26, 2026

Copy link
Copy Markdown
Author

Went through CodeRabbit's review. Here's where each point landed:

  • Read each file on its own so one bad file doesn't abort the batch (Major). Done in ef9bccb. Each file goes through its own readMultipleFiles([file]) call, so a malformed JSON only fails that file and the valid ones still import. CodeRabbit already marked this one resolved.
  • Normalize the name once before dedupe and import (Major). The name is sanitized once in the named/unnamed split and reused for the dedupe key and for both the collection and global payloads, so the two branches can't diverge. I also added the type guard CodeRabbit suggested (3d81bff): a non-string name no longer reaches sanitizeName and throws, and a name that sanitizes to empty is now counted as unnamed instead of importing a blank-named environment.
  • Test coverage for the batch outcomes. Added a jest spec next to the component (ImportEnvironmentModal/index.spec.js, 9 tests) covering mixed Bruno/Postman, a malformed file alongside valid ones, duplicate-name skips, unnamed environments, non-string and empty names, a mid-batch dispatch failure, the "nothing valid" path, and the sanitized name in both import payloads. I kept it as a unit spec rather than extending the tests/environments/import-environment Playwright suite, because it asserts the toast summary and the imported/skipped/unnamed/failures counts directly.
  • Unnamed-environment test fidelity. The Bruno importer defaults a missing name to "Imported Environment", so I moved the unnamed case to the Postman path, which is where a genuinely nameless environment actually comes from.

Attachments:

  • bruno-env-import-demo.zip: the six environment files I tested with (valid Bruno and Postman exports, a duplicate name, a malformed JSON, and a valid-but-not-an-environment file). Dropping all six at once imports 3, skips the duplicate, and reports the 2 bad files instead of failing the whole batch.
bruno-fix.environments.mp4

bruno-env-import-demo.zip

  • A short screen recording showing the before/after.

@NicolasCV

Copy link
Copy Markdown
Author

Pushed 79ce252 for the two follow-up review comments.

The within-batch duplicate path now has its own test. Two environments in the same drop whose names sanitize to the same value ('Prod/Env' and 'Prod-Env' both become 'Prod-Env'): the first one imports, the second is skipped, and neither gets a 'copy' suffix. It asserts the toast summary and that dispatch only fires once, so the seen.add(key) branch is actually exercised rather than only the collision-against-existing case.

I also made the Bruno importer mock faithful to the real one. It now normalizes a missing name to 'Imported Environment' before returning, the same as validateBrunoEnvironment, instead of handing the raw env straight to processEnvironments. With that fixed I added a test that an unnamed Bruno environment imports under that default name rather than being counted as "had no name". The genuinely nameless case still goes through the Postman path, since that is where an environment with no name actually shows up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Importing multiple environment files is fragile: one bad file aborts the batch, partial failures show a generic error, and re-import stacks duplicates

1 participant